<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>MinIO Plus Demo</title>
</head>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="js/spark-md5.js"></script>
<script src="js/upload.js"></script>
<body>
<div id="app">
    <input type="file" @click="clearState" id="upload">
    <button @click="checkFile">检查</button>
    <button @click="uploadFile(false)" :disabled="partList.length === 0">正常上传</button>
    <button @click="uploadFile(true)" :disabled="partList.length < 2">模拟丢片上传</button>
    <button @click="recover" :disabled="missChunkNumber == null">丢片恢复</button>
    <button @click="remove" :disabled="uploadId == null">删除文件</button>
    <div>
        <input type="text" v-model="uploadId" id="uploadId">
        <button @click="merge" :disabled="uploadId == null">合并分片</button>
    </div>
    <div>
        <button @click="preview" :disabled="uploadId == null">预览图片</button>
        <button @click="download" :disabled="uploadId == null">下载文件</button>
        <img :src="previewUrl">
    </div>
    <div>
        <label>总计片数：{{partList.length}}</label>
        <div v-for="(item,index) in partList" :style="{color:missChunkNumber === index ? 'red' : 'black'}">
            <label>第{{index + 1}}片:</label>
            <br/>
            <label>{{item.url}}</label>
        </div>
    </div>

</div>
</body>
</html>

<script>
    const {createApp, reactive, toRefs, onMounted} = Vue
    createApp({
        setup() {
            const state = reactive({
                uploadId: null,
                partList: [],
                missChunkNumber: null,
                partCount: null,
                partSize: null,
                fileSize: null,
                previewUrl: null,
            })
            /**
             * 文件检查
             * @returns {Promise<void>}
             */
            const checkFile = async () => {
                //获取用户选择的文件
                const file = document.getElementById("upload").files[0];
                //获取文件md5
                let startTime = new Date();
                const fileMd5 = await getFileMd5(file)

                console.log("文件md5：" + fileMd5 + "，耗时" + (new Date() - startTime) + "毫秒");

                console.log("向后端请求本次分片上传初始化")

                fetch("/storage/upload/init", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json"
                    },
                    body: JSON.stringify({
                        fileMd5: fileMd5,
                        fullFileName: file.name,
                        fileSize: file.size
                    })
                }).then(res => res.json()).then(({data}) => {
                    console.log(data);
                    // 获取文件上传id
                    state.uploadId = data.fileKey;
                    // 获取文件分片
                    state.partList = data.partList;
                    // 获取文件大小
                    state.fileSize = data.fileSize;
                    // 获取块大小
                    state.partSize = data.partSize;
                    // 获取文件分片数
                    state.partCount = data.partCount;
                }).catch(err => {
                    console.log(err);
                })
            }
            /**
             * 上传文件
             * @param isMissingPart 是否模拟丢片
             * @returns {Promise<void>}
             */
            const uploadFile = async (isMissingPart) => {
                if (isMissingPart) {
                    // 随机一个缺失分片
                    state.missChunkNumber = Math.floor(Math.random() * state.partList.length);
                } else {
                    state.missChunkNumber = null;
                }
                state.fileChunkMd5 = [];
                const file = document.getElementById("upload").files[0];
                for (const [i, item] of state.partList.entries()) {
                    //取文件指定范围内的byte，从而得到分片数据
                    let _chunkFile = file.slice(item.startPosition, item.endPosition)
                    if (i === state.missChunkNumber) {
                        console.log("丢弃第" + i + "个分片")
                        continue;
                    }
                    console.log("开始上传第" + i + "个分片", _chunkFile)
                    // 上传分片
                    fetch(item.url, {
                        method: "PUT",
                        body: _chunkFile,
                    }).then(res => {
                        console.log("第" + i + "个分片上传完成")
                    }).catch(err => {
                        console.log(err);
                    })
                }

                console.log(state.fileChunkMd5);
            }

            /**
             * 请求后端合并文件
             */
            const merge = async () => {
                console.log("开始请求后端合并文件")
                // 每块文件MD5
                const partMd5List = [];
                // 获取用户选择的文件
                const file = document.getElementById("upload").files[0];
                for (let i = 0; i < state.partCount; i++) {
                    console.log(i)
                    let _chunkFile;
                    if (i === state.missChunkNumber) {
                        continue;
                    }
                    if (i === state.partCount - 1) {
                        _chunkFile = file.slice(i * state.partSize, state.fileSize)
                    } else {
                        _chunkFile = file.slice(i * state.partSize, (i + 1) * state.partSize)
                    }
                    let partMd5 = await getFileMd5(_chunkFile);
                    partMd5List.push(partMd5);
                }
                //
                fetch(`/storage/upload/complete/${state.uploadId}`, {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json"
                    },
                    body: JSON.stringify({
                        partMd5List: partMd5List
                    })
                }).then(res => res.json()).then(({data}) => {
                    console.log("合并文件完成", data)
                }).catch(err => {
                    console.log(err);
                });
            }

            const recover = async () => {
                await checkFile();
                await uploadFile(false);
                await merge();
            }
            const clearState = async () => {
                state.uploadId = null;
                state.partList = [];
                state.fileChunkMd5 = [];
                state.missChunkNumber = null
            }

            /**
             * 下载文件
             */
            const download = () => {
                window.location.href = `/storage/download/${state.uploadId}`;
            }
            // 文件预览
            const preview = () => {
                state.previewUrl = `/storage/preview/original/${state.uploadId}`;
            }
            // 删除文件
            const remove = () => {
                fetch(`/storage/remove/${state.uploadId}`, {
                    method: "POST",
                }).then(res => res.json()).then(({data}) => {
                    console.log("删除文件完成", data)
                }).catch(err => {
                    console.log(err);
                });
            }
            /**
             * 获取文件MD5
             * @param file
             * @returns {Promise<unknown>}
             */
            const getFileMd5 = (file) => {
                let fileReader = new FileReader()
                fileReader.readAsBinaryString(file)
                let spark = new SparkMD5()
                return new Promise((resolve) => {
                    fileReader.onload = (e) => {
                        spark.appendBinary(e.target.result)
                        resolve(spark.end())
                    }
                })
            }


            return {
                checkFile,
                removeTaskId,
                clearState,
                uploadFile,
                merge,
                download,
                preview,
                remove,
                recover,
                ...toRefs(state)
            }
        }
    }).mount('#app')
</script>
